package com.mnsk.generator.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mnsk.generator.common.utils.file.FileUtils;
import com.mnsk.generator.common.utils.text.CharsetKit;
import com.mnsk.generator.common.utils.text.Convert;
import com.mnsk.generator.domain.SysDataSource;
import com.mnsk.generator.domain.Table;
import com.mnsk.generator.domain.TableColumn;
import com.mnsk.generator.domain.Template;
import com.mnsk.generator.mapper.*;
import com.mnsk.generator.service.*;
import com.mnsk.generator.common.aspectj.lang.enums.DataSourceType;
import com.mnsk.generator.common.cache.GenCacheUtils;
import com.mnsk.generator.common.constant.Constants;
import com.mnsk.generator.common.constant.GenConstants;
import com.mnsk.generator.common.constant.StringConstants;
import com.mnsk.generator.common.exception.BusinessException;
import com.mnsk.generator.common.util.GenUtils;
import com.mnsk.generator.common.util.VelocityUtils;
import com.mnsk.generator.common.utils.StringUtils;
import com.mnsk.generator.config.datasource.DynamicDataSourceContextHolder;
import com.mnsk.generator.query.TableQuery;
import com.mnsk.generator.service.*;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 业务 服务层实现
 *
 * @author admin
 */
@Service
public class TableServiceImpl extends ServiceImpl<TableMapper, Table> implements TableService {

    @Resource
    private MySQLMapper mysqlMapper;
    @Resource
    private OracleMapper oracleMapper;
    @Resource
    private DmMapper dmMapper;
    @Resource
    private PostgreSQLMapper postgresqlMapper;
    @Resource
    private SysDataSourceService dataSourceService;
    @Resource
    private TableColumnService tableColumnService;
    @Resource
    private TemplateService templateService;

    /**
     * 根据业务id获取业务表结构（包含列）
     *
     * @param id 业务ID
     * @return 业务信息
     */
    @Override
    public Table selectGenTableById(Long id) {
        Table table = this.baseMapper.selectGenTableById(id);
        return table;
    }

    /**
     * 根据数据库类型获取对应的mapper
     *
     * @param dbType 数据库类型
     */
    private BaseService getSlaveMapper(String dbType) {
        if (Constants.DATABASE_TYPE_MYSQL.equals(dbType)) {
            return mysqlMapper;
        } else if (Constants.DATABASE_TYPE_ORACLE.equals(dbType)) {
            return oracleMapper;
        } else if (Constants.DATABASE_TYPE_POSTGRESQL.equals(dbType)) {
            return postgresqlMapper;
        } else if (Constants.DATABASE_TYPE_DM.equals(dbType)) {
            return dmMapper;
        }
        return mysqlMapper;
    }

    /**
     * 根据表名获取列
     *
     * @param dataSourceId 数据源主键
     * @param tableName    表名
     */
    private List<TableColumn> selectDbTableColumnsByName(Long dataSourceId, String tableName) {
        SysDataSource dataSource = dataSourceService.getById(dataSourceId);
        try {
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name() + Convert.toStr(dataSource.getId()));
            return getSlaveMapper(dataSource.getDbType()).selectDbTableColumnsByName(tableName);
        } catch (Exception e) {
            // 有异常不抛出，返回空集合，防止无法回滚
            return new ArrayList<>();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    /**
     * 查询据库列表
     *
     * @param tableQuery 业务信息
     * @return 数据库表集合
     */
    @Override
    public List<Table> selectDbTableList(TableQuery tableQuery) {
        try {
            SysDataSource dataSource = dataSourceService.getById(tableQuery.getDataSourceId());
            //切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name() + Convert.toStr(dataSource.getId()));
            return getSlaveMapper(dataSource.getDbType()).selectDbTableList(tableQuery);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    /**
     * 查询据库列表
     *
     * @param tableNames   表名称组
     * @param dataSourceId 数据源主键
     * @return 数据库表集合
     */
    @Override
    public List<Table> selectDbTableListByNames(String[] tableNames, Long dataSourceId) {
        SysDataSource dataSource = dataSourceService.getById(dataSourceId);
        try {
            //切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE.name() + Convert.toStr(dataSource.getId()));
            return getSlaveMapper(dataSource.getDbType()).selectDbTableListByNames(tableNames);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    /**
     * 获取指定数据源所有表结构（包含列）
     *
     * @param dataSourceId 数据源主键
     * @return 表信息集合
     */
    @Override
    public List<Table> selectGenTableAll(Long dataSourceId) {
        return this.baseMapper.selectGenTableAll(dataSourceId);
    }

    /**
     * 修改业务
     *
     * @param table 业务信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateGenTable(Table table) {
        String options = JSON.toJSONString(table.getParams());
        table.setOptions(options);
        int row = this.baseMapper.updateById(table);
        if (row > 0) {
            tableColumnService.updateBatchById(table.getColumns());
        }
    }

    /**
     * 删除业务对象
     *
     * @param ids 需要删除的数据ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteGenTableByIds(List<Long> ids) {
        this.baseMapper.deleteBatchIds(ids);
        tableColumnService.deleteGenTableColumnByTableId(ids);
    }

    /**
     * 导入表结构
     *
     * @param tableList    导入表列表
     * @param dataSourceId 数据源主键
     */
    @Override
    public void importGenTable(List<Table> tableList, Long dataSourceId) {
        SysDataSource dataSource = dataSourceService.getById(dataSourceId);
        for (Table table : tableList) {
            try {
                String tableName = table.getTableName();
                GenUtils.initTable(table);
                table.setDataSourceId(dataSourceId);
                int row = this.baseMapper.insert(table);
                if (row > 0) {
                    List<TableColumn> dbTableColumns = selectDbTableColumnsByName(dataSource.getId(), tableName);
                    for (TableColumn column : dbTableColumns) {
                        GenUtils.initColumnField(column, table);
                    }
                    tableColumnService.saveBatch(dbTableColumns);
                }
            } catch (Exception e) {
                throw new BusinessException("表名 " + table.getTableName() + " 导入失败!", e);
            }
        }
    }

    /**
     * 预览代码
     *
     * @param tableId 表编号
     * @return 预览数据列表
     */
    @Override
    public Map<String, String> previewCode(Long tableId) {
        // 查询表信息
        Table table = this.baseMapper.selectGenTableById(tableId);
        return getTemplateMap(table);
    }

    /**
     * 生成代码（下载方式）
     *
     * @param tableName 表名称
     * @return 数据
     */
    @Override
    public byte[] downloadCode(String tableName) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        generatorCode(tableName, zip);
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * 生成代码（自定义路径）
     *
     * @param tableName 表名称
     */
    @Override
    public void generatorCode(String tableName) {
        // 查询表信息
        Table table = this.baseMapper.selectGenTableByName(tableName);
        // 获取模板列表并填充字段信息
        Map<String, String> templates = getTemplateMap(table);
        for (Map.Entry<String, String> template : templates.entrySet()) {
            if (!StringUtils.contains(template.getKey(), "sql")) {
                try {
                    String path = getGenPath(table, template.getKey());
                    FileUtils.writeStringToFile(new File(path), template.getValue(), CharsetKit.UTF_8);
                } catch (IOException e) {
                    throw new BusinessException("渲染模板失败，表名：" + table.getTableName(), e);
                }
            }
        }
    }

    /**
     * 从数据库同步更新表结构
     *
     * @param tableName 表名称
     * @param tableId   表Id
     */
    @Override
    public void syncDb(String tableName, Long tableId) {
        try {
            // 从当前系统获取表业务信息
            LambdaQueryWrapper<Table> queryWrapper = Wrappers.lambdaQuery();
            queryWrapper.eq(Table::getTableName, tableName);
            queryWrapper.eq(Table::getTableId, tableId);
            Table table = this.baseMapper.selectOne(queryWrapper);
            // 移除表字段
            tableColumnService.deleteGenTableColumnByTableId(Collections.singletonList(table.getTableId()));
            // 从数据源获取表业务信息
            SysDataSource dataSource = dataSourceService.getById(table.getDataSourceId());
            List<TableColumn> dbTableColumns = selectDbTableColumnsByName(dataSource.getId(), tableName);
            for (TableColumn column : dbTableColumns) {
                GenUtils.initColumnField(column, table);
            }
            // 重新添加表字段
            tableColumnService.saveBatch(dbTableColumns);
        } catch (Exception e) {
            // 发生异常手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            throw new BusinessException("数据源或表结构获取失败！请检查数据源与表名是否有效！", e);
        }
    }

    /**
     * 批量生成代码
     *
     * @param tableNames 表数组
     * @return 数据
     */
    @Override
    public byte[] downloadCode(String[] tableNames) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        for (String tableName : tableNames) {
            generatorCode(tableName, zip);
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * 查询表信息并生成代码
     */
    private void generatorCode(String tableName, ZipOutputStream zip) {
        // 查询表信息
        Table table = this.baseMapper.selectGenTableByName(tableName);
        // 获取模板列表
        Map<String, String> templates = getTemplateMap(table);
        for (Map.Entry<String, String> template : templates.entrySet()) {
            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template.getKey(), table)));
                IOUtils.write(template.getValue(), zip, StandardCharsets.UTF_8);
                zip.flush();
                zip.closeEntry();
            } catch (IOException e) {
                throw new BusinessException(template.getKey() + " zip压缩出现问题");
            }
        }
    }

    /**
     * 修改保存参数校验
     *
     * @param table 业务信息
     */
    @Override
    public void validateEdit(Table table) {
        if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
            if (StringUtils.isEmpty(table.getSubTableName())) {
                throw new BusinessException("关联子表的表名不能为空");
            } else if (StringUtils.isEmpty(table.getSubTableFkName())) {
                throw new BusinessException("子表关联的外键名不能为空");
            }
        }
    }

    /**
     * 设置主键列信息
     *
     * @param table 业务表信息
     */
    public void setPkColumn(Table table) {
        // 循环表的字段集合找到主键
        for (TableColumn column : table.getColumns()) {
            if (column.isPk()) {
                table.setPkColumn(column);
                break;
            }
        }
        // 判断主键是否为空，如果为空就将第一字段放入主键集合
        if (StringUtils.isNull(table.getPkColumn())) {
            table.setPkColumn(table.getColumns().get(0));
        }
        // 判断类型是不是主子表（增删改查）
        if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
            for (TableColumn column : table.getSubTable().getColumns()) {
                if (column.isPk()) {
                    table.getSubTable().setPkColumn(column);
                    break;
                }
            }
            if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
            }
        }
    }

    /**
     * 获取代码生成地址
     *
     * @param table    业务表信息
     * @param template 模板文件路径
     * @return 生成地址
     */
    public static String getGenPath(Table table, String template) {
        String genPath = table.getGenPath();
        if (StringUtils.equals(genPath, File.separator)) {
            return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
        }
        return genPath + File.separator + VelocityUtils.getFileName(template, table);
    }

    /**
     * 根据业务表获取模版Map集合
     *
     * @return 模版Map集合
     */
    private Map<String, String> getTemplateMap(Table table) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        // 设置主子表信息
        setSubTable(table);
        // 设置主键列信息
        setPkColumn(table);
        // 获取当前模版类型
        String templateType = GenCacheUtils.getGenTemplateType();
        // 获得当前模板类型的模板
        List<Template> templateList = templateService.selectTemplateByType(templateType, table.getTplCategory());
        // 设置模板变量信息
        VelocityContext context = VelocityUtils.prepareContext(table);
        templateList.forEach(template -> {
            try (StringWriter sw = new StringWriter()) {
                String logTag = template.getTemplateType() + StringConstants.FORWARD_SLASH + template.getTemplateName();
                // 模板内容,模板写入流,模板出错日志模板名,模板内容
                Velocity.evaluate(context, sw, logTag, template.getTemplateContent());
                dataMap.put(template.getTemplateName(), sw.toString());
            } catch (Exception e) {
                throw new BusinessException("渲染模板【" + template.getTemplateName() + "】出现错误");
            }
        });
        // 返回模板集合
        return dataMap;
    }

    /**
     * 设置主子表信息
     *
     * @param table 业务表信息
     */
    public void setSubTable(Table table) {
        String subTableName = table.getSubTableName();
        if (StringUtils.isNotEmpty(subTableName)) {
            table.setSubTable(this.baseMapper.selectGenTableByName(subTableName));
        }
    }
}
